package query.insert

import ast.expr.SqlIdentifierExpr
import ast.statement.insert.SqlInsert
import database.DB
import dsl.QueryTableColumn
import dsl.TableSchema
import query.ReviseQuery
import util.toSqlString
import visitor.getExpr
import java.sql.Connection
import kotlin.reflect.KProperty
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.declaredMembers
import kotlin.reflect.jvm.javaField

/**
 * insert语句dsl类
 * @property db DB 数据库类型
 * @property conn Connection? 数据库连接
 * @property isTransaction Boolean 是否是事务
 * @property sqlInsert SqlInsert insert语法树
 * @property columns MutableList<String> 插入字段名列表
 * @property records MutableList<Any> 插入记录列表
 * @property incrKey String 自增主键名
 */
class Insert(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlInsert = SqlInsert()

    private var columns = mutableListOf<String>()

    private var records = mutableListOf<Any>()

    private var incrKey = ""

    private var autoGenerateKey = ""

    private var generateKeyFunction: () -> Any = {}

    /**
     * insert into子句
     * 例如：Insert() into Table
     * @param table TableSchema 实体类伴生对象名
     * @return Insert 插入dsl
     */
    infix fun <T : TableSchema> into(table: T): Insert {
        sqlInsert.table = SqlIdentifierExpr(table.tableName)

        val clazz = table::class
        val declaredMemberProperties = clazz.declaredMemberProperties

        val properties = declaredMemberProperties.map { it.name to it.getter.call(table) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter {
                if (it.second.incr) {
                    incrKey = it.first
                }
                !it.second.incr
            }

        properties.forEach {
            if (it.second.autoGenerateKey) {
                autoGenerateKey = it.first
                generateKeyFunction = it.second.generateFunction
            }

            columns.add(it.first)
            sqlInsert.columns.add(SqlIdentifierExpr(it.second.column))
        }

        return this
    }

    /**
     * 使用实体数据生成sql（实体类需要定义正确的伴生对象）
     * 例如：Insert() into entity
     * @param entity Any 实体数据
     * @return Insert 插入dsl
     */
    infix fun insert(entity: Any): Insert {
        val clazz = entity::class
        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        into(companion as TableSchema)

        value(entity)

        return this
    }

    /**
     * 使用实体数据列表生成sql（实体类需要定义正确的伴生对象）
     * 例如：Insert() into listOf(entity1, entity2)
     * @param entity List<Any> 实体数据列表
     * @return Insert 插入dsl
     */
    infix fun insert(entity: List<Any>): Insert {
        if (entity.isNotEmpty()) {
            val clazz = entity[0]::class
            val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
            into(companion as TableSchema)

            values(entity)
        }

        return this
    }

    /**
     * values子句
     * 例如：Insert() into Table value entity
     * @param obj Any 实体对象
     * @return Insert 插入dsl
     */
    infix fun value(obj: Any): Insert {
        val clazz = obj::class
        val properties = clazz.declaredMemberProperties.map { it.name to it.getter.call(obj) }.toMap()
        val value = columns.map {
            if (it == autoGenerateKey) {
                val key = generateKeyFunction()
                val field = (clazz.declaredMembers.find { c -> c.name == autoGenerateKey } as KProperty).javaField
                field?.isAccessible = true
                field?.set(obj, key)

                getExpr(key)
            } else {
                getExpr(properties[it])
            }
        }

        sqlInsert.values.add(value)

        records.add(obj)

        return this
    }

    /**
     * values子句（批量）
     * 例如：Insert() into Table values listOf(entity1, entity2)
     * @param objList List<T> 实体对象列表
     * @return Insert 插入dsl
     */
    infix fun <T : Any> values(objList: List<T>): Insert {
        objList.forEach {
            value(it)
        }

        return this
    }

    /**
     * values子句（批量）
     * 例如：Insert().into(Table).values(entity1, entity2)
     * @param obj Array<out T> 实体对象列表
     * @return Insert 插入dsl
     */
    fun <T : Any> values(vararg obj: T): Insert {
        return values(obj.toList())
    }

    /**
     * 生成sql语句
     * @return String sql语句
     */
    override fun sql() = toSqlString(sqlInsert, db)

    /**
     * 执行插入语句并返回受影响行数（如果实体类伴生对象定义了自增主键，插入成功后会替换掉实体对象中对应字段的值）
     * @return Int 受影响行数
     */
    override fun exec(): Int {
        val result = database.execReturnKey(conn!!, this.sql())
        if (!isTransaction) {
            conn!!.close()
        }

        result.forEachIndexed { index, item ->
            val clazz = records[index]::class
            val field = (clazz.declaredMembers.find { it.name == incrKey } as KProperty).javaField
            field?.isAccessible = true
            val convert = convertIncrKey(item, field?.type?.typeName!!)
            field.set(records[index], convert)
        }
        return this.sqlInsert.values.size
    }

    /**
     * @param key Long
     * @param javaType String
     * @return Any
     */
    private fun convertIncrKey(key: Long, javaType: String): Any {
        return when (javaType) {
            "java.lang.Byte" -> key.toByte()
            "java.lang.Short" -> key.toShort()
            "java.lang.Integer" -> key.toInt()
            "java.lang.Long" -> key
            else -> key.toString()
        }
    }
}